package com.yugao.fintech.antelope.gateway.service;

import com.alibaba.fastjson2.JSON;
import com.yugao.fintech.antelope.base.config.core.ConfigFactory;
import com.yugao.fintech.antelope.base.config.core.ConfigMsg;
import com.yugao.fintech.antelope.base.config.core.IConfigListener;
import com.yugao.fintech.antelope.gateway.handler.AbstractFileHandler;
import com.yugao.fintech.antelope.base.model.constants.RequestCons;
import com.yugao.fintech.antelope.base.model.enums.ConfigDefineEnum;
import com.yugao.fintech.antelope.base.model.enums.ConfigKeyEnum;
import com.yugao.fintech.antelope.base.model.enums.StorageTypeEnum;
import com.yugao.fintech.antelope.base.model.module.config.ConfigInfo;
import com.yugao.fintech.antelope.base.model.module.config.param.BaseConfigParam;
import com.yugao.fintech.antelope.base.model.module.config.param.StorageParam;
import com.yugao.fintech.antelope.base.model.module.tenant.TenantContextHolder;
import com.yugao.fintech.framework.assistant.utils.CollectionUtils;
import com.yugao.fintech.framework.assistant.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


@Slf4j
@Service
public class FileServiceImpl implements FileService, IConfigListener {

    /**
     * 文件访问url业务对象, 存储在数据库中的文件路径baseUrl格式为 http://业务编码.存储桶.file
     * 桶名就是文件的根路径
     *
     * 该正则含义: 获取 http://[...]file 之间的内容, 包含 http:// 和 file
     */
    private static final String REG = "http://.*?.file";
    private static final Pattern PATTERN = Pattern.compile(REG);

    /**
     * key: tenantId
     */
    public static final Map<String, StorageParam<?>> storageParamCache = new ConcurrentHashMap<>();

    @Autowired
    private ConfigFactory configFactory;

    @Override
    public String virtualToRealBaseUrl(ServerHttpRequest request, String data) {
        String originEndpoint = Optional.ofNullable(request.getHeaders().getFirst(RequestCons.Headers.ORIGIN_ENDPOINT)).orElse("");
        String tenantId = Optional.ofNullable(request.getHeaders().getFirst(RequestCons.Headers.TENANT_ID)).orElse("");
        if (StringUtils.isEmpty(tenantId)) {
            log.warn("has not will file url to real from virtual, [ tenantId is empty ]");
            return data;
        }
        return this.virtualToRealBaseUrl(tenantId, data, originEndpoint);
    }

    @Override
    public String realToVirtualBaseUrl(ServerHttpRequest request, String data) {
        String originEndpoint = Optional.ofNullable(request.getHeaders().getFirst(RequestCons.Headers.ORIGIN_ENDPOINT)).orElse("");
        String tenantId = Optional.ofNullable(request.getHeaders().getFirst(RequestCons.Headers.TENANT_ID)).orElse("");
        TenantContextHolder.get().setTenantId(tenantId);
        if (StringUtils.isEmpty(tenantId)) {
            log.warn("has not will file url to virtual from real, [ tenantId is empty ]");
            return data;
        }
        return this.realToVirtualBaseUrl(tenantId, data, originEndpoint);
    }

    /**
     * 真实baseUrl替换成成虚拟baseUrl, 一般用于请求数据
     *
     * 替换被上传的文件baseUrl, 在一些需要保存上传的文件路径的接口中, 由于在响应的时候, 网关会将 http://业务编码.桶名.file 前缀
     * 替换成真实的baseUrl, 但是前端在编辑表单的时候, 如果没有修改上传的文件, 直接点保存, 这时候需要将真实的地址再替换成 http://业务编码.桶名.file
     */
    public String realToVirtualBaseUrl(String tenantId, String data, String originEndpoint) {
        TenantContextHolder.get().setTenantId(tenantId);

        StorageParam<?> storageParam = storageParamCache.get(tenantId);
        if (Objects.isNull(storageParam)) {
            storageParam = (StorageParam<?>) createStorageParam();
        }
        StorageParam.Domain domain = getDomain(tenantId, originEndpoint, storageParam);
        if (Objects.isNull(domain)) {
            return data;
        }

        AbstractFileHandler instance = getFileHandler(storageParam);
        if (Objects.isNull(instance)) {
            return data;
        }

        return instance.realToVirtualBaseUrl(data, storageParam, domain.getDomain());
    }

    private StorageParam.Domain getDomain(String tenantId, String originEndpoint, StorageParam<?> storageParam) {
        if (Objects.isNull(storageParam) || CollectionUtils.isEmpty(storageParam.getDomains())) {
            log.warn("未配置文件存储器配置参数, tenantId: {}", tenantId);
            return null;
        }

        // 获取映射的域名
        StorageParam.Domain domain = storageParam.getDomains().stream()
                .filter(e -> e.getEndpointValue().equalsIgnoreCase(originEndpoint)).findFirst().orElse(null);
        if (Objects.isNull(domain)) {
            log.warn("租户尚未配置文件存储域名, tenantId: {}, originEndpoint: {}", tenantId, originEndpoint);
            return null;
        }
        return domain;
    }

    private AbstractFileHandler getFileHandler(StorageParam<?> storageParam) {
        // 校验存储类型
        StorageTypeEnum storageType = StorageTypeEnum.ofByBindConfig(storageParam.configKey());
        if (Objects.isNull(storageType)) {
            log.warn("storage type code [{}] 没有定义", storageParam.configKey());
            return null;
        }

        // 校验文件处理器
        AbstractFileHandler instance = AbstractFileHandler.getInstance(storageType);
        if (Objects.isNull(instance)) {
            log.warn("未实现类型为 [{}] 的文件处理器", storageType.name());
            return null;
        }
        return instance;
    }

    /**
     * 虚拟baseUrl替换成成真实baseUrl, 一般用于响应数据
     * @param respData 响应数据
     * @param originEndpoint 请求头中 {@link RequestCons.Headers#ORIGIN_ENDPOINT} 的值
     * @return 替换结果
     */
    public String virtualToRealBaseUrl(String tenantId, String respData, String originEndpoint) {
        TenantContextHolder.get().setTenantId(tenantId);

        Matcher matcher = PATTERN.matcher(respData.replace("http://127.0.0.1", "").replace("http://localhost", ""));
        String virtualBaseUrl = "";
        // 这里只需要查找第一个即可, 因为正常的业务接口如果需要上传文件, 无论上传多少个文件都会存储到一个存储厂商(minio/oss)中且也会存储到
        // 同一个桶中
        if( matcher.find() ){
            virtualBaseUrl = matcher.group();
        }
        if (StringUtils.isEmpty(virtualBaseUrl)) {
            return respData;
        }

        // 获取业务编码并获取配置
        try {
            String[] split = virtualBaseUrl
                    .replace("http://", "")
                    .replace(".file", "")
                    .split("\\.");
            if (split.length != 2) {
                return respData;
            }
            String bucketName = split[1];

            // 获取配置
            StorageParam<?> storageParam = storageParamCache.get(tenantId);
            if (storageParam == null) {
                storageParam = (StorageParam<?>) createStorageParam();
            }
            StorageParam.Domain domain = getDomain(tenantId, originEndpoint, storageParam);
            if (Objects.isNull(domain)) {
                return respData;
            }

            // 校验存储类型
            AbstractFileHandler instance = getFileHandler(storageParam);
            if (Objects.isNull(instance)) {
                return respData;
            }

            return respData.replace(virtualBaseUrl, instance.getAccessUrl(bucketName, domain));
        } catch (Exception e) {
            return respData;
        }
    }

    public BaseConfigParam<?> createStorageParam() {
        ConfigInfo config = configFactory.getConfig(ConfigDefineEnum.STORAGE);
        if (Objects.isNull(config)) {
            return null;
        }
        String tenantId = String.valueOf(TenantContextHolder.get().getTenantId());
        BaseConfigParam<?> baseConfigParam;
        synchronized (tenantId.intern()) {
            baseConfigParam = storageParamCache.get(tenantId);
            if (Objects.nonNull(baseConfigParam)) {
                return baseConfigParam;
            }
            ConfigKeyEnum configKey = ConfigKeyEnum.ofAndCheck(config.getConfigKey());
            baseConfigParam = BaseConfigParam.jsonToObject(configKey, config.getConfigValue());
            storageParamCache.put(tenantId, (StorageParam<?>) baseConfigParam);
        }
        return baseConfigParam;
    }

    @Override
    public void updateConfig(ConfigMsg msg) {
        log.info("delete config: {}", JSON.toJSONString(msg));
        storageParamCache.remove(String.valueOf(msg.getTenantId()));
    }

    @Override
    public ConfigDefineEnum configDefine() {
        return ConfigDefineEnum.STORAGE;
    }
}
